/*******************************************************************************
 * Copyright (c) 2002 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.ui.dialogs;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import net.sourceforge.perforce.ui.PerforceUIPlugin;
import net.sourceforge.perforce.ui.internal.Policy;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.TableLayout;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;

/**
  * @version $Revision: 196 $
  * @author  <a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  */
abstract class AbstractChangeSpecDialog extends Dialog 
{
	// --------------------------------------------------------------------------- Private Constants
	
	/** Constant: Width Hint for Files Table */
	private static final int WIDTH_HINT = 450;
	/** Constant: Height Hint for Files Table */
	private static final int HEIGHT_HINT = 100;

	// --------------------------------------------------------------------------- Private Variables

	/** Text Field: description text */
	private Text descriptionText;
	
	/** Table Viewer: Files */		
	private TableViewer viewer;
	
	/** Stores the background of the status area. */
	private Color statusBackground;
	
	/** Stores the bold font */
	private Font statusFont;
	
	/** Stores the images (for disposing) */
	private List images = new LinkedList();

	/** Holds the dialogs position. */
	private Rectangle dialogPosition;

	// ------------------------------------------------------------------------- Public Constructors
	
	/** Constructor for AbstractChangeSpecDialog.
	  *
	  * @param shell the parent shell. 
	  */
	public AbstractChangeSpecDialog(Shell shell)
	{
		super(shell);
		setShellStyle(SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.RESIZE);
	}

	// ------------------------------------------------------------------------------ Public Methods

	/** @see org.eclipse.jface.window.Window#create() */
	public void create() 
	{
		super.create();
		
		readSettings();
		
		Shell shell = getShell();			
		if (dialogPosition != null)
		{
			shell.setBounds(dialogPosition);
		}
	}

	/** @see org.eclipse.jface.dialogs.Dialog#close() */
	public boolean close() 
	{
		storeSettings();
		boolean result = super.close();
		
		statusBackground.dispose();
		statusFont.dispose();
		
		for (Iterator it = images.iterator(); it.hasNext();)
		{
			Image image = (Image) it.next();
			image.dispose();
		}
		
		return result;
	}
			
		
	// --------------------------------------------------------------------------- Protected Methods
	
	/** @see Dialog#createDialogArea(Composite) */
	protected Control createDialogArea(Composite parent)
	{
		getShell().setText(getTitle());
		
		Composite composite = new Composite(parent, SWT.NULL);
		GridLayout layout = new GridLayout();
		layout.numColumns = 1;
		composite.setLayout(layout);
		composite.setLayoutData(new GridData(GridData.FILL_BOTH));
		
		createStatusArea(composite);
		
		// Description Area
		Label descriptionLabel = 
			createLabel(composite, Policy.bind("ChangeSpecDialog.description"));       //$NON-NLS-1$
		GridData data = new GridData(GridData.FILL_HORIZONTAL);
		descriptionLabel.setLayoutData(data);
		
		int style = SWT.BORDER | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL;
		descriptionText = createDescriptionTextControl(composite, style);
		descriptionText.addKeyListener(new KeyAdapter() 
		{
			public void keyPressed(KeyEvent e) 
			{
				String description = descriptionText.getText();
				if (description.equals("<enter description here>"))
				{
					getButton(IDialogConstants.OK_ID).setEnabled(false);
				}
				else
				{
					getButton(IDialogConstants.OK_ID).setEnabled(true);					
				}
			}
		});
		
		data = new GridData(GridData.FILL_BOTH);
		data.widthHint = WIDTH_HINT;
		data.heightHint = HEIGHT_HINT;
		descriptionText.setLayoutData(data);
		descriptionText.setText(getDescription());
		
		descriptionText.selectAll();
		descriptionText.setFocus();
		
		// Files Area
		Label filesLabel =
			createLabel(composite, Policy.bind("ChangeSpecDialog.files"));             //$NON-NLS-1$
		data = new GridData(GridData.FILL_HORIZONTAL);
		filesLabel.setLayoutData(data);
		
		createTable(composite);
		
		String[] files = getFiles();
		viewer.add(files);
		
		TableItem[] items = viewer.getTable().getItems();
		for (int i = 0, length = items.length; i < length; i++)
		{
			items[i].setChecked(true);
			
			String filename = files[i].substring(files[i].lastIndexOf('/') + 1);			
			ImageDescriptor descriptor =
				PlatformUI.getWorkbench().getEditorRegistry().getImageDescriptor(filename);
			Image image = descriptor.createImage();
			images.add(image);
			items[i].setImage(image);
		}
		
		createTableButtonBar(composite);

		Label separator2 = new Label(composite, SWT.HORIZONTAL | SWT.SEPARATOR);
		data = new GridData(GridData.FILL_HORIZONTAL);
		separator2.setLayoutData(data);		

		return composite;		
	}
	
	/** Called to create the button areas placed under the files area.
	  *
	  * @param parent the parent composite. 
	  */
	protected void createTableButtonBar(Composite parent) 
	{
	}
	
	/** Called to retrieve the file viewer.
	  *
	  * @return the file viewer. 
	  */ 
	protected TableViewer getFileViewer()
	{
		return viewer;
	}

	// ------------------------------------------------------------------------------------- Methods

	/** Called to create the description text control.
	  *
	  * @param parent the parent composite.
	  * @param style the style.
	  * @return the text control. 
	  */
	protected Text createDescriptionTextControl(Composite parent, int style) 
	{
		return new Text(parent, style);
	}
	
	/** Called to create the files table control.
	  * 
	  * @param parent the parent composite.
	  * @param style the style.
	  * @return the table control. 
	  */
	protected Table createFileTableControl(Composite parent, int style) 
	{
		return new Table(parent, style);
	}
		
	/** Returns the currently entered description text.
	  *
	  * @return the description text. 
	  */
	String getDescriptionText()
	{
		return descriptionText.getText();
	}
	
	/** Called to retrieve an array of files that are 'checked'
	  *
	  * @return array of selected files. 
	  */
	String[] getSelectedFiles()
	{
		List result = new ArrayList();
		
		TableItem[] items = viewer.getTable().getItems();
		for (int i = 0, length = items.length; i < length; i++)
		{
			if (items[i].getChecked())
			{
				result.add(items[i].getText());
			}
		}
		
		return (String[]) result.toArray(new String[result.size()]);
	}
	
	/** Returns the file count.
	  *
	  * @return number of files. 
	  */
	int getFileCount()
	{
		TableItem[] items = viewer.getTable().getItems();
		return items.length;
	}
	
	/** Called to retrieve the description to display.
	  * 
	  * @return the description.
	  */
	abstract String getDescription();
	
	/** Called to retrieve the title of the dialog.
	  *
	  * @return the dialog's title. 
	  */
	abstract String getTitle();
	
	/** Called to retrieve the changelist from the specdata.
	  *
	  * @return the changelist. 
	  */
	abstract String getChangelist();

	/** Called to retrieve the date from the specdata.
	  *
	  * @return the date. 
	  */
	abstract String getDate();

	/** Called to retrieve the client name from the specdata.
	  *
	  * @return the client name. 
	  */
	abstract String getClientName();
	
	/** Called to retrieve the user name from the specdata.
	  *
	  * @return the user name. 
	  */
	abstract String getUserName();

	/** Called to retrieve the changelist status from the specdata.
	  *
	  * @return the status. 
	  */
	abstract String getStatus();
	
	/** Retrieves an array of files stored in the specdata.
	  *
	  * @return array of file specs 
	  */ 
	abstract String[] getFiles();
	
	// ----------------------------------------------------------------------------- Private Methods
	
	/** Called to create the status area.
	  *
	  * @param parent the parent composite. 
	  */
	private void createStatusArea(Composite parent) 
	{ 
		StatusComposite composite = new StatusComposite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.numColumns = 5;
		composite.setLayout(layout);
		
		GridData data = new GridData(GridData.FILL_HORIZONTAL);
		composite.setLayoutData(data);
		
		statusBackground = new Color(composite.getDisplay(), new RGB(255, 255, 204));
		composite.setBackground(statusBackground);
		
		// Build Status Area
		Label l = createLabel(composite, Policy.bind("ChangeSpecDialog.change"));	   //$NON-NLS-1$
		l.setBackground(statusBackground);
		
		FontData fontData = l.getFont().getFontData()[0];
		fontData.setStyle(SWT.BOLD);
		statusFont = new Font(composite.getDisplay(), fontData);
		l.setFont(statusFont);	
		
		
		l = createLabel(composite, Policy.bind("ChangeSpecDialog.date"));              //$NON-NLS-1$
		l.setBackground(statusBackground);
		l.setFont(statusFont);
		
		l = createLabel(composite, Policy.bind("ChangeSpecDialog.client"));            //$NON-NLS-1$
		l.setBackground(statusBackground);
		l.setFont(statusFont);
		
		l = createLabel(composite, Policy.bind("ChangeSpecDialog.user"));              //$NON-NLS-1$
		l.setBackground(statusBackground);
		l.setFont(statusFont);
		
		l = createLabel(composite, Policy.bind("ChangeSpecDialog.status"));            //$NON-NLS-1$
		l.setBackground(statusBackground);
		l.setFont(statusFont);
		
		l = createLabel(composite, getChangelist());
		l.setBackground(statusBackground);
				
		l = createLabel(composite, getDate());
		l.setBackground(statusBackground);
				
		l = createLabel(composite, getClientName());
		l.setBackground(statusBackground);
						
		l = createLabel(composite, getUserName());
		l.setBackground(statusBackground);
						
		l = createLabel(composite, getStatus());
		l.setBackground(statusBackground);
	}		

	/** Called to create a text label with the given text.
	  *
	  * @param parent the parent composite.
	  * @param text the text.
	  * @return the Label object. 
	  */
	private Label createLabel(Composite parent, String text)
	{
		Label result = new Label(parent, SWT.NULL);
		result.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL));
		result.setText(text);		
		
		return result;
	}
	
	/** Called to create the File table.
	  *
	  * @param parent the parent composite. 
	  */
	private void createTable(Composite parent) 
	{	
		int style = SWT.BORDER | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL;
		style |= SWT.CHECK;

		final Table table = createFileTableControl(parent, style);		
		
		TableLayout layout = new TableLayout();
		layout.addColumnData(new ColumnWeightData(1));
		
		table.setLayout(layout);
		table.setLinesVisible(true);
		
		viewer = new TableViewer(table);
		
		GridData data = new GridData(GridData.FILL_BOTH);
		data.widthHint = WIDTH_HINT;
		data.heightHint = HEIGHT_HINT;

		table.setLayoutData(data);
		
		// Add KeyListener to emulate P4Win's selection behavior
		table.addKeyListener(new KeyAdapter() 
		{
			public void keyPressed(KeyEvent event)
			{
				if (event.character == ' ')
				{
					int index = table.getSelectionIndex();
					TableItem item = table.getItem(index);
					
					boolean selection =  item.getChecked();
					TableItem[] items = table.getSelection();
					for (int i = 0; i < items.length; i++)
					{
						items[i].setChecked(selection);
					}
				}
			}
		});
	}

	/** Returns the dialog's boundaries.
	  * 
	  * @return the dialog's boundaries
	  */
	private Rectangle getDialogBoundaries() 
	{
		if (okToUse(getShell())) 
		{
			return getShell().getBounds();
		} 
		else 
		{
			return dialogPosition;
		}
	}
	
	/** Returns <code>true</code> if control can be used
	  *
	  * @param control the control to be checked
	  * @return <code>true</code> if control can be used
	  */
	private boolean okToUse(Control control) 
	{
		return control != null && !control.isDisposed();
	}

	/** Retrieves the associated IDialogSettings.
	  *
	  * @return the dialog settings. 
	  */
	private IDialogSettings getDialogSettings()
	{
		IDialogSettings pluginSettings = PerforceUIPlugin.getPlugin().getDialogSettings();
		IDialogSettings dialogSettings = pluginSettings.getSection(getClass().getName());
		
		if (dialogSettings == null)
		{
			dialogSettings = pluginSettings.addNewSection(getClass().getName());
		}
			
		return dialogSettings;
	}
	
	/** Reads the dialogs settings. */ 
	private void readSettings()
	{
		IDialogSettings s = getDialogSettings();
		try
		{
			int x = s.getInt("x");                                                     //$NON-NLS-1$
			int y = s.getInt("y");                                                     //$NON-NLS-1$
			int width = s.getInt("width");                                             //$NON-NLS-1$
			int height = s.getInt("height");                                           //$NON-NLS-1$
			dialogPosition = new Rectangle(x, y, width, height);
		}
		catch (NumberFormatException e) 
		{
			dialogPosition = null;
		}
	}
	
	/** Stores the dialogs settings. */
	private void storeSettings()
	{		
		IDialogSettings s = getDialogSettings();

		Rectangle dialogPosition = getDialogBoundaries();
		s.put("x", dialogPosition.x);                                                  //$NON-NLS-1$
		s.put("y", dialogPosition.y);                                                  //$NON-NLS-1$
		s.put("width", dialogPosition.width);                                          //$NON-NLS-1$
		s.put("height", dialogPosition.height);                                        //$NON-NLS-1$
	}
}
